<template>
  <view class="uni-forms" :class="{ 'uni-forms--top': !border }">
    <form @submit.stop="submitForm" @reset="resetForm">
      <slot></slot>
    </form>
  </view>
</template>

<script>
// #ifndef VUE3
import Vue from 'vue';

Vue.prototype.binddata = function (name, value, formName) {
  if (formName) {
    this.$refs[formName].setValue(name, value);
  } else {
    let formVm;
    for (let i in this.$refs) {
      const vm = this.$refs[i];
      if (vm && vm.$options && vm.$options.name === 'uniForms') {
        formVm = vm;
        break;
      }
    }
    if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
    formVm.setValue(name, value);
  }
};
// #endif


import Validator from './validate.js';

/**
 * Forms 表单
 * @description 由输入框、选择器、单选框、多选框等控件组成，用以收集、校验、提交数据
 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
 * @property {Object} rules  表单校验规则
 * @property {String} validateTrigger = [bind|submit]	校验触发器方式 默认 submit
 * @value bind		发生变化时触发
 * @value submit	提交时触发
 * @property {String} labelPosition = [top|left]	label 位置 默认 left
 * @value top		顶部显示 label
 * @value left	左侧显示 label
 * @property {String} labelWidth  label 宽度，默认 65px
 * @property {String} labelAlign = [left|center|right]	label 居中方式  默认 left
 * @value left		label 左侧显示
 * @value center	label 居中
 * @value right		label 右侧对齐
 * @property {String} errShowType = [undertext|toast|modal]	校验错误信息提示方式
 * @value undertext	错误信息在底部显示
 * @value toast			错误信息toast显示
 * @value modal			错误信息modal显示
 * @event {Function} submit  提交时触发
 */

export default {
  name: 'uniForms',
  components: {},
  emits: ['input', 'reset', 'validate', 'submit'],
  props: {
    // 即将弃用
    value: {
      type: Object,
      default() {
        return {};
      }
    },
    // 替换 value 属性
    modelValue: {
      type: Object,
      default() {
        return {};
      }
    },
    // 表单校验规则
    rules: {
      type: Object,
      default() {
        return {};
      }
    },
    // 校验触发器方式，默认 关闭
    validateTrigger: {
      type: String,
      default: ''
    },
    // label 位置，可选值 top/left
    labelPosition: {
      type: String,
      default: 'left'
    },
    // label 宽度，单位 px
    labelWidth: {
      type: [String, Number],
      default: ''
    },
    // label 居中方式，可选值 left/center/right
    labelAlign: {
      type: String,
      default: 'left'
    },
    errShowType: {
      type: String,
      default: 'undertext'
    },
    border: {
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      formData: {}
    };
  },
  computed: {
    dataValue() {
      if (JSON.stringify(this.modelValue) === '{}') {
        return this.value
      } else {
        return this.modelValue
      }
    }
  },
  watch: {
    rules(newVal) {
      // 如果规则发生变化，要初始化组件
      this.init(newVal);
    },
    labelPosition() {
      this.childrens.forEach(vm => {
        vm.init()
      })
    }
  },
  created() {
    // #ifdef VUE3
    let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata
    if (!getbinddata) {
      getApp().$vm.$.appContext.config.globalProperties.binddata = function (name, value, formName) {
        if (formName) {
          this.$refs[formName].setValue(name, value);
        } else {
          let formVm;
          for (let i in this.$refs) {
            const vm = this.$refs[i];
            if (vm && vm.$options && vm.$options.name === 'uniForms') {
              formVm = vm;
              break;
            }
          }
          if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
          formVm.setValue(name, value);
        }
      }
    }
    // #endif

    // 存放watch 监听数组
    this.unwatchs = [];
    // 存放子组件数组
    this.childrens = [];
    // 存放 easyInput 组件
    this.inputChildrens = [];
    // 存放 dataCheckbox 组件
    this.checkboxChildrens = [];
    // 存放规则
    this.formRules = [];
    this.init(this.rules);
  },
  // mounted() {
  // 	this.init(this.rules)
  // },
  methods: {
    init(formRules) {
      // 判断是否有规则
      if (Object.keys(formRules).length === 0) {
        this.formData = this.dataValue
        return
      }
      ;
      this.formRules = formRules;
      this.validator = new Validator(formRules);
      this.registerWatch();
    },
    // 监听 watch
    registerWatch() {
      // 取消监听,避免多次调用 init 重复执行 $watch
      this.unwatchs.forEach(v => v());
      this.childrens.forEach((v) => {
        v.init()
      })
      // watch 每个属性 ，需要知道具体那个属性发变化
      Object.keys(this.dataValue).forEach(key => {
        let watch = this.$watch(
            'dataValue.' + key,
            value => {
              if (!value) return
              // 如果是对象 ，则平铺内容
              if (value.toString() === '[object Object]') {
                for (let i in value) {
                  let name = `${key}[${i}]`;
                  this.formData[name] = this._getValue(name, value[i]);
                }
              } else {
                this.formData[key] = this._getValue(key, value);
              }
            },
            {
              deep: true,
              immediate: true
            }
        );
        this.unwatchs.push(watch);
      });
    },
    /**
     * 公开给用户使用
     * 设置校验规则
     * @param {Object} formRules
     */
    setRules(formRules) {
      this.init(formRules);
    },
    /**
     * 公开给用户使用
     * 设置自定义表单组件 value 值
     *  @param {String} name 字段名称
     *  @param {String} value 字段值
     */
    setValue(name, value, callback) {
      let example = this.childrens.find(child => child.name === name);
      if (!example) return null;
      value = this._getValue(example.name, value);
      this.formData[name] = value;
      example.val = value;
      return example.triggerCheck(value, callback);
    },

    /**
     * 表单重置
     * @param {Object} event
     */
    resetForm(event) {
      this.childrens.forEach(item => {
        item.errMsg = '';
        const inputComp = this.inputChildrens.find(child => child.rename === item.name);
        if (inputComp) {
          inputComp.errMsg = '';
          // fix by mehaotian 不触发其他组件的 setValue
          inputComp.is_reset = true
          inputComp.$emit('input', inputComp.multiple ? [] : '');
          inputComp.$emit('update:modelValue', inputComp.multiple ? [] : '');
        }
      });

      this.childrens.forEach(item => {
        if (item.name) {
          this.formData[item.name] = this._getValue(item.name, '');
        }
      });

      this.$emit('reset', event);
    },

    /**
     * 触发表单校验，通过 @validate 获取
     * @param {Object} validate
     */
    validateCheck(validate) {
      if (validate === null) validate = null;
      this.$emit('validate', validate);
    },
    /**
     * 校验所有或者部分表单
     */
    async validateAll(invalidFields, type, keepitem, callback) {
      let childrens = []
      for (let i in invalidFields) {
        const item = this.childrens.find(v => v.name === i)
        if (item) {
          childrens.push(item)
        }
      }

      if (!callback && typeof keepitem === 'function') {
        callback = keepitem;
      }

      let promise;
      if (!callback && typeof callback !== 'function' && Promise) {
        promise = new Promise((resolve, reject) => {
          callback = function (valid, invalidFields) {
            !valid ? resolve(invalidFields) : reject(valid);
          };
        });
      }

      let results = [];
      let newFormData = {};
      if (this.validator) {
        for (let key in childrens) {
          const child = childrens[key];
          let name = child.isArray ? child.arrayField : child.name;
          if (child.isArray) {
            if (child.name.indexOf('[') !== -1 && child.name.indexOf(']') !== -1) {
              const fieldData = child.name.split('[');
              const fieldName = fieldData[0];
              const fieldValue = fieldData[1].replace(']', '');
              if (!newFormData[fieldName]) {
                newFormData[fieldName] = {};
              }
              newFormData[fieldName][fieldValue] = this._getValue(name, invalidFields[name]);
            }
          } else {
            newFormData[name] = this._getValue(name, invalidFields[name]);
          }
          const result = await child.triggerCheck(invalidFields[name], true);
          if (result) {
            results.push(result);
            if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
          }
        }
      } else {
        newFormData = invalidFields
      }
      if (Array.isArray(results)) {
        if (results.length === 0) results = null;
      }

      if (Array.isArray(keepitem)) {
        keepitem.forEach(v => {
          newFormData[v] = this.dataValue[v];
        });
      }

      if (type === 'submit') {
        this.$emit('submit', {
          detail: {
            value: newFormData,
            errors: results
          }
        });
      } else {
        this.$emit('validate', results);
      }

      callback && typeof callback === 'function' && callback(results, newFormData);

      if (promise && callback) {
        return promise;
      } else {
        return null;
      }
    },
    submitForm() {
    },
    /**
     * 外部调用方法
     * 手动提交校验表单
     * 对整个表单进行校验的方法，参数为一个回调函数。
     */
    submit(keepitem, callback, type) {
      for (let i in this.dataValue) {
        const itemData = this.childrens.find(v => v.name === i);
        if (itemData) {
          if (this.formData[i] === undefined) {
            this.formData[i] = this._getValue(i, this.dataValue[i]);
          }
        }
      }
      if (!type) {
        console.warn('submit 方法即将废弃，请使用validate方法代替！');
      }
      return this.validateAll(this.formData, 'submit', keepitem, callback);
    },

    /**
     * 外部调用方法
     * 校验表单
     * 对整个表单进行校验的方法，参数为一个回调函数。
     */
    validate(keepitem, callback) {
      return this.submit(keepitem, callback, true);
    },

    /**
     * 部分表单校验
     * @param {Object} props
     * @param {Object} cb
     */
    validateField(props, callback) {
      props = [].concat(props);
      let invalidFields = {};
      this.childrens.forEach(item => {
        if (props.indexOf(item.name) !== -1) {
          invalidFields = Object.assign({}, invalidFields, {
            [item.name]: this.formData[item.name]
          });
        }
      });
      return this.validateAll(invalidFields, 'submit', [], callback);
    },

    /**
     * 对整个表单进行重置，将所有字段值重置为初始值并移除校验结果
     */
    resetFields() {
      this.resetForm();
    },

    /**
     * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组，如不传则移除整个表单的校验结果
     */
    clearValidate(props) {
      props = [].concat(props);
      this.childrens.forEach(item => {
        const inputComp = this.inputChildrens.find(child => child.rename === item.name);
        if (props.length === 0) {
          item.errMsg = '';
          if (inputComp) {
            inputComp.errMsg = '';
          }
        } else {
          if (props.indexOf(item.name) !== -1) {
            item.errMsg = '';
            if (inputComp) {
              inputComp.errMsg = '';
            }
          }
        }
      });
    },
    /**
     * 把 value 转换成指定的类型
     * @param {Object} key
     * @param {Object} value
     */
    _getValue(key, value) {
      const rules = (this.formRules[key] && this.formRules[key].rules) || [];
      const isRuleNum = rules.find(val => val.format && this.type_filter(val.format));
      const isRuleBool = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool');
      // 输入值为 number
      if (isRuleNum) {
        value = isNaN(value) ? value : value === '' || value === null ? null : Number(value);
      }
      // 简单判断真假值
      if (isRuleBool) {
        value = !value ? false : true;
      }
      return value;
    },
    /**
     * 过滤数字类型
     * @param {Object} format
     */
    type_filter(format) {
      return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp';
    }
  }
};
</script>

<style lang="scss">
.uni-forms {
  // overflow: hidden;
  // padding: 10px 15px;
}

.uni-forms--top {
  // padding: 10px 15px;
  // padding-top: 22px;
}
</style>
